package edu.northwestern.cbits.purple_robot_manager.probes.features.p20;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.math3.analysis.interpolation.SplineInterpolator;
import org.apache.commons.math3.analysis.polynomials.PolynomialSplineFunction;
import android.util.Log;
import edu.emory.mathcs.backport.java.util.Arrays;
public class FeatureExtractor
{
private long _windowSize = -1;
private int _dimensions = -1;
private boolean _hasFFT = false;
private boolean _hasDiff = false;
private boolean _hasHist = false;
private boolean _hasCross = false;
private boolean _hasNormCross = false;
// private FastFourierTransformer _fft = null;
// bin edges must be in ascending order and equally spaced.
private double[] _binEdges = new double[]
{ -3, -2, -1, 0, 1, 2, 3 };
public enum Feature
{
ACC_NUM_SAMPLES, ACC_MEAN, ACCX_MEAN, ACCY_MEAN, ACCZ_MEAN, ACC_MEAN_ABS, ACCX_MEAN_ABS, ACCY_MEAN_ABS, ACCZ_MEAN_ABS, ACCX_STD, ACCY_STD, ACCZ_STD, ACCX_SKEW, ACCY_SKEW, ACCZ_SKEW, ACCX_KURT, ACCY_KURT, ACCZ_KURT, ACCX_DIFF_MEAN, ACCY_DIFF_MEAN, ACCZ_DIFF_MEAN, ACCX_DIFF_STD, ACCY_DIFF_STD, ACCZ_DIFF_STD, ACCX_DIFF_SKEW, ACCY_DIFF_SKEW, ACCZ_DIFF_SKEW, ACCX_DIFF_KURT, ACCY_DIFF_KURT, ACCZ_DIFF_KURT, ACCX_MAX, ACCY_MAX, ACCZ_MAX, ACCX_MIN, ACCY_MIN, ACCZ_MIN, ACCX_MAX_ABS, ACCY_MAX_ABS, ACCZ_MAX_ABS, ACCX_MIN_ABS, ACCY_MIN_ABS, ACCZ_MIN_ABS, ACCX_RMS, ACCY_RMS, ACCZ_RMS, ACC_CROSS_XY, ACC_CROSS_YZ, ACC_CROSS_ZX, ACC_CROSS_XY_ABS, ACC_CROSS_YZ_ABS, ACC_CROSS_ZX_ABS, ACC_CROSS_XY_NORM, ACC_CROSS_YZ_NORM, ACC_CROSS_ZX_NORM, ACC_CROSS_XY_NORM_ABS, ACC_CROSS_YZ_NORM_ABS, ACC_CROSS_ZX_NORM_ABS, ACCX_FFT1, ACCX_FFT2, ACCX_FFT3, ACCX_FFT4, ACCX_FFT5, ACCX_FFT6, ACCX_FFT7, ACCX_FFT8, ACCX_FFT9, ACCX_FFT10, ACCY_FFT1, ACCY_FFT2, ACCY_FFT3, ACCY_FFT4, ACCY_FFT5, ACCY_FFT6, ACCY_FFT7, ACCY_FFT8, ACCY_FFT9, ACCY_FFT10, ACCZ_FFT1, ACCZ_FFT2, ACCZ_FFT3, ACCZ_FFT4, ACCZ_FFT5, ACCZ_FFT6, ACCZ_FFT7, ACCZ_FFT8, ACCZ_FFT9, ACCZ_FFT10, ACCX_HIST1, ACCX_HIST2, ACCX_HIST3, ACCX_HIST4, ACCX_HIST5, ACCX_HIST6, ACCY_HIST1, ACCY_HIST2, ACCY_HIST3, ACCY_HIST4, ACCY_HIST5, ACCY_HIST6, ACCZ_HIST1, ACCZ_HIST2, ACCZ_HIST3, ACCZ_HIST4, ACCZ_HIST5, ACCZ_HIST6, GYR_NUM_SAMPLES, GYR_MEAN, GYRX_MEAN, GYRY_MEAN, GYRZ_MEAN, GYR_MEAN_ABS, GYRX_MEAN_ABS, GYRY_MEAN_ABS, GYRZ_MEAN_ABS, GYRX_STD, GYRY_STD, GYRZ_STD, GYRX_SKEW, GYRY_SKEW, GYRZ_SKEW, GYRX_KURT, GYRY_KURT, GYRZ_KURT, GYRX_DIFF_MEAN, GYRY_DIFF_MEAN, GYRZ_DIFF_MEAN, GYRX_DIFF_STD, GYRY_DIFF_STD, GYRZ_DIFF_STD, GYRX_DIFF_SKEW, GYRY_DIFF_SKEW, GYRZ_DIFF_SKEW, GYRX_DIFF_KURT, GYRY_DIFF_KURT, GYRZ_DIFF_KURT, GYRX_MAX, GYRY_MAX, GYRZ_MAX, GYRX_MIN, GYRY_MIN, GYRZ_MIN, GYRX_MAX_ABS, GYRY_MAX_ABS, GYRZ_MAX_ABS, GYRX_MIN_ABS, GYRY_MIN_ABS, GYRZ_MIN_ABS, GYRX_RMS, GYRY_RMS, GYRZ_RMS, GYR_CROSS_XY, GYR_CROSS_YZ, GYR_CROSS_ZX, GYR_CROSS_XY_ABS, GYR_CROSS_YZ_ABS, GYR_CROSS_ZX_ABS, GYR_CROSS_XY_NORM, GYR_CROSS_YZ_NORM, GYR_CROSS_ZX_NORM, GYR_CROSS_XY_NORM_ABS, GYR_CROSS_YZ_NORM_ABS, GYR_CROSS_ZX_NORM_ABS, GYRX_FFT1, GYRX_FFT2, GYRX_FFT3, GYRX_FFT4, GYRX_FFT5, GYRX_FFT6, GYRX_FFT7, GYRX_FFT8, GYRX_FFT9, GYRX_FFT10, GYRY_FFT1, GYRY_FFT2, GYRY_FFT3, GYRY_FFT4, GYRY_FFT5, GYRY_FFT6, GYRY_FFT7, GYRY_FFT8, GYRY_FFT9, GYRY_FFT10, GYRZ_FFT1, GYRZ_FFT2, GYRZ_FFT3, GYRZ_FFT4, GYRZ_FFT5, GYRZ_FFT6, GYRZ_FFT7, GYRZ_FFT8, GYRZ_FFT9, GYRZ_FFT10, GYRX_HIST1, GYRX_HIST2, GYRX_HIST3, GYRX_HIST4, GYRX_HIST5, GYRX_HIST6, GYRY_HIST1, GYRY_HIST2, GYRY_HIST3, GYRY_HIST4, GYRY_HIST5, GYRY_HIST6, GYRZ_HIST1, GYRZ_HIST2, GYRZ_HIST3, GYRZ_HIST4, GYRZ_HIST5, GYRZ_HIST6, PROCESSING_TIME
}
public FeatureExtractor(long windowSize, List<Feature> features, int dimensions)
{
this._windowSize = windowSize;
this._dimensions = dimensions;
for (Feature f : features)
{
String featureName = f.toString();
if (featureName.contains("FFT"))
{
this._hasFFT = true;
// this._fft = new
// FastFourierTransformer(DftNormalization.STANDARD);
}
else if (featureName.contains("DIFF"))
this._hasDiff = true;
else if (featureName.contains("HIST"))
this._hasHist = true;
else if (featureName.contains("CROSS"))
{
this._hasCross = true;
if (featureName.contains("NORM"))
this._hasNormCross = true;
}
}
}
public void setBinEdges(double[] edges)
{
this._binEdges = Arrays.copyOf(edges, edges.length);
}
public Map<Feature, Double> extractFeatures(Clip clp)
{
HashMap<Feature, Double> features = new HashMap<>();
// build a copy of the clip. because it sometimes crashes suspiciously.
Clip clip = new Clip(clp);
// Spline Interpolation
List<double[]> signal = this.interpolate(clip.getValues(), clip.getTimestamps(), 50);
// Calculating the statistical moments
double[] mean = new double[this._dimensions];
double[] std = new double[this._dimensions];
double[] skewness = new double[this._dimensions];
double[] kurtosis = new double[this._dimensions];
for (int i = 0; i < this._dimensions; i++)
{
double[] moments = this.getMoments(signal, i);
mean[i] = moments[0];
std[i] = moments[1];
skewness[i] = moments[2];
kurtosis[i] = moments[3];
}
switch (clip.getType())
{
case Clip.ACCELEROMETER:
features.put(Feature.ACC_NUM_SAMPLES, (double) signal.size());
features.put(Feature.ACC_MEAN, this.getOverallMean(signal));
features.put(Feature.ACCX_MAX, this.getMax(signal, 0));
features.put(Feature.ACCY_MAX, this.getMax(signal, 1));
features.put(Feature.ACCZ_MAX, this.getMax(signal, 2));
features.put(Feature.ACCX_MIN, this.getMin(signal, 0));
features.put(Feature.ACCY_MIN, this.getMin(signal, 1));
features.put(Feature.ACCZ_MIN, this.getMin(signal, 2));
features.put(Feature.ACCX_MAX_ABS, Math.abs(this.getMax(signal, 0)));
features.put(Feature.ACCY_MAX_ABS, Math.abs(this.getMax(signal, 1)));
features.put(Feature.ACCZ_MAX_ABS, Math.abs(this.getMax(signal, 2)));
features.put(Feature.ACCX_MIN_ABS, Math.abs(this.getMin(signal, 0)));
features.put(Feature.ACCY_MIN_ABS, Math.abs(this.getMin(signal, 1)));
features.put(Feature.ACCZ_MIN_ABS, Math.abs(this.getMin(signal, 2)));
features.put(Feature.ACCX_MEAN, mean[0]);
features.put(Feature.ACCY_MEAN, mean[1]);
features.put(Feature.ACCZ_MEAN, mean[2]);
features.put(Feature.ACCX_MEAN_ABS, Math.abs(mean[0]));
features.put(Feature.ACCY_MEAN_ABS, Math.abs(mean[1]));
features.put(Feature.ACCZ_MEAN_ABS, Math.abs(mean[2]));
features.put(Feature.ACCX_STD, std[0]);
features.put(Feature.ACCY_STD, std[1]);
features.put(Feature.ACCZ_STD, std[2]);
features.put(Feature.ACCX_SKEW, skewness[0]);
features.put(Feature.ACCY_SKEW, skewness[1]);
features.put(Feature.ACCZ_SKEW, skewness[2]);
features.put(Feature.ACCX_KURT, kurtosis[0]);
features.put(Feature.ACCY_KURT, kurtosis[1]);
features.put(Feature.ACCZ_KURT, kurtosis[2]);
features.put(Feature.ACCX_RMS, this.getRMS(signal, 0));
features.put(Feature.ACCY_RMS, this.getRMS(signal, 1));
features.put(Feature.ACCZ_RMS, this.getRMS(signal, 2));
break;
case Clip.GYROSCOPE:
features.put(Feature.GYR_NUM_SAMPLES, (double) signal.size());
features.put(Feature.GYR_MEAN, this.getOverallMean(signal));
features.put(Feature.GYRX_MAX, this.getMax(signal, 0));
features.put(Feature.GYRY_MAX, this.getMax(signal, 1));
features.put(Feature.GYRZ_MAX, this.getMax(signal, 2));
features.put(Feature.GYRX_MIN, this.getMin(signal, 0));
features.put(Feature.GYRY_MIN, this.getMin(signal, 1));
features.put(Feature.GYRZ_MIN, this.getMin(signal, 2));
features.put(Feature.GYRX_MAX_ABS, Math.abs(this.getMax(signal, 0)));
features.put(Feature.GYRY_MAX_ABS, Math.abs(this.getMax(signal, 1)));
features.put(Feature.GYRZ_MAX_ABS, Math.abs(this.getMax(signal, 2)));
features.put(Feature.GYRX_MIN_ABS, Math.abs(this.getMin(signal, 0)));
features.put(Feature.GYRY_MIN_ABS, Math.abs(this.getMin(signal, 1)));
features.put(Feature.GYRZ_MIN_ABS, Math.abs(this.getMin(signal, 2)));
features.put(Feature.GYRX_MEAN, mean[0]);
features.put(Feature.GYRY_MEAN, mean[1]);
features.put(Feature.GYRZ_MEAN, mean[2]);
features.put(Feature.GYRX_MEAN_ABS, Math.abs(mean[0]));
features.put(Feature.GYRY_MEAN_ABS, Math.abs(mean[1]));
features.put(Feature.GYRZ_MEAN_ABS, Math.abs(mean[2]));
features.put(Feature.GYRX_STD, std[0]);
features.put(Feature.GYRY_STD, std[1]);
features.put(Feature.GYRZ_STD, std[2]);
features.put(Feature.GYRX_SKEW, skewness[0]);
features.put(Feature.GYRY_SKEW, skewness[1]);
features.put(Feature.GYRZ_SKEW, skewness[2]);
features.put(Feature.GYRX_KURT, kurtosis[0]);
features.put(Feature.GYRY_KURT, kurtosis[1]);
features.put(Feature.GYRZ_KURT, kurtosis[2]);
features.put(Feature.GYRX_RMS, this.getRMS(signal, 0));
features.put(Feature.GYRY_RMS, this.getRMS(signal, 1));
features.put(Feature.GYRZ_RMS, this.getRMS(signal, 2));
break;
}
if (this._hasDiff)
{
double[] diffMean = new double[this._dimensions];
double[] diffStd = new double[this._dimensions];
double[] diffSkewness = new double[this._dimensions];
double[] diffKurtosis = new double[this._dimensions];
List<double[]> signalDiff = new ArrayList<>();
signalDiff = this.getDiff(signal);
// Calculating the statistical moments of the difference signal
for (int i = 0; i < this._dimensions; i++)
{
double[] moments = this.getMoments(signalDiff, i);
diffMean[i] = moments[0];
diffStd[i] = moments[1];
diffSkewness[i] = moments[2];
diffKurtosis[i] = moments[3];
}
switch (clip.getType())
{
case Clip.ACCELEROMETER:
features.put(Feature.ACCX_DIFF_MEAN, diffMean[0]);
features.put(Feature.ACCY_DIFF_MEAN, diffMean[1]);
features.put(Feature.ACCZ_DIFF_MEAN, diffMean[2]);
features.put(Feature.ACCX_DIFF_STD, diffStd[0]);
features.put(Feature.ACCY_DIFF_STD, diffStd[1]);
features.put(Feature.ACCZ_DIFF_STD, diffStd[2]);
features.put(Feature.ACCX_DIFF_SKEW, diffSkewness[0]);
features.put(Feature.ACCY_DIFF_SKEW, diffSkewness[1]);
features.put(Feature.ACCZ_DIFF_SKEW, diffSkewness[2]);
features.put(Feature.ACCX_DIFF_KURT, diffKurtosis[0]);
features.put(Feature.ACCY_DIFF_KURT, diffKurtosis[1]);
features.put(Feature.ACCZ_DIFF_KURT, diffKurtosis[2]);
break;
case Clip.GYROSCOPE:
features.put(Feature.GYRX_DIFF_MEAN, diffMean[0]);
features.put(Feature.GYRY_DIFF_MEAN, diffMean[1]);
features.put(Feature.GYRZ_DIFF_MEAN, diffMean[2]);
features.put(Feature.GYRX_DIFF_STD, diffStd[0]);
features.put(Feature.GYRY_DIFF_STD, diffStd[1]);
features.put(Feature.GYRZ_DIFF_STD, diffStd[2]);
features.put(Feature.GYRX_DIFF_SKEW, diffSkewness[0]);
features.put(Feature.GYRY_DIFF_SKEW, diffSkewness[1]);
features.put(Feature.GYRZ_DIFF_SKEW, diffSkewness[2]);
features.put(Feature.GYRX_DIFF_KURT, diffKurtosis[0]);
features.put(Feature.GYRY_DIFF_KURT, diffKurtosis[1]);
features.put(Feature.GYRZ_DIFF_KURT, diffKurtosis[2]);
break;
}
}
if (this._hasHist)
{
// histogram of zscore values
int[][] hist = new int[this._dimensions][this._binEdges.length - 1];
int bin = 0;
List<double[]> signalZScore = this.getZScore(signal, mean, std);
for (int i = 0; i < this._dimensions; i++)
{
for (int j = 0; j < this._binEdges.length - 1; j++)
hist[i][j] = 0;
// TODO: Pull out into it's own variable: signalZScore.size()
for (int j = 0; j < signalZScore.size(); j++)
{
bin = (int) ((signalZScore.get(j)[i] - this._binEdges[0]) / (this._binEdges[1] - this._binEdges[0]));
if ((bin < this._binEdges.length - 1) && (bin >= 0)) // values
// outside
// the
// range
// are
// neglected
hist[i][bin]++;
}
}
// TODO
// Add another set of histograms on raw signals (not zscore)
// TBD also on MATLAB side
switch (clip.getType())
{
case Clip.ACCELEROMETER:
features.put(Feature.ACCX_HIST1, (double) hist[0][0]);
features.put(Feature.ACCX_HIST2, (double) hist[0][1]);
features.put(Feature.ACCX_HIST3, (double) hist[0][2]);
features.put(Feature.ACCX_HIST4, (double) hist[0][3]);
features.put(Feature.ACCX_HIST5, (double) hist[0][4]);
features.put(Feature.ACCX_HIST6, (double) hist[0][5]);
features.put(Feature.ACCY_HIST1, (double) hist[1][0]);
features.put(Feature.ACCY_HIST2, (double) hist[1][1]);
features.put(Feature.ACCY_HIST3, (double) hist[1][2]);
features.put(Feature.ACCY_HIST4, (double) hist[1][3]);
features.put(Feature.ACCY_HIST5, (double) hist[1][4]);
features.put(Feature.ACCY_HIST6, (double) hist[1][5]);
features.put(Feature.ACCZ_HIST1, (double) hist[2][0]);
features.put(Feature.ACCZ_HIST2, (double) hist[2][1]);
features.put(Feature.ACCZ_HIST3, (double) hist[2][2]);
features.put(Feature.ACCZ_HIST4, (double) hist[2][3]);
features.put(Feature.ACCZ_HIST5, (double) hist[2][4]);
features.put(Feature.ACCZ_HIST6, (double) hist[2][5]);
break;
case Clip.GYROSCOPE:
features.put(Feature.GYRX_HIST1, (double) hist[0][0]);
features.put(Feature.GYRX_HIST2, (double) hist[0][1]);
features.put(Feature.GYRX_HIST3, (double) hist[0][2]);
features.put(Feature.GYRX_HIST4, (double) hist[0][3]);
features.put(Feature.GYRX_HIST5, (double) hist[0][4]);
features.put(Feature.GYRX_HIST6, (double) hist[0][5]);
features.put(Feature.GYRY_HIST1, (double) hist[1][0]);
features.put(Feature.GYRY_HIST2, (double) hist[1][1]);
features.put(Feature.GYRY_HIST3, (double) hist[1][2]);
features.put(Feature.GYRY_HIST4, (double) hist[1][3]);
features.put(Feature.GYRY_HIST5, (double) hist[1][4]);
features.put(Feature.GYRY_HIST6, (double) hist[1][5]);
features.put(Feature.GYRZ_HIST1, (double) hist[2][0]);
features.put(Feature.GYRZ_HIST2, (double) hist[2][1]);
features.put(Feature.GYRZ_HIST3, (double) hist[2][2]);
features.put(Feature.GYRZ_HIST4, (double) hist[2][3]);
features.put(Feature.GYRZ_HIST5, (double) hist[2][4]);
features.put(Feature.GYRZ_HIST6, (double) hist[2][5]);
break;
}
}
if (this._hasCross)
{
double[] cross = new double[this._dimensions];
for (int i = 0; i < this._dimensions; i++)
cross[i] = 0;
if (this._dimensions == 3)
{
cross = this.get3DInnerProds(signal);
switch (clip.getType())
{
case Clip.ACCELEROMETER:
features.put(Feature.ACC_CROSS_XY, cross[0]);
features.put(Feature.ACC_CROSS_YZ, cross[1]);
features.put(Feature.ACC_CROSS_ZX, cross[2]);
features.put(Feature.ACC_CROSS_XY_ABS, Math.abs(cross[0]));
features.put(Feature.ACC_CROSS_YZ_ABS, Math.abs(cross[1]));
features.put(Feature.ACC_CROSS_ZX_ABS, Math.abs(cross[2]));
break;
case Clip.GYROSCOPE:
features.put(Feature.GYR_CROSS_XY, cross[0]);
features.put(Feature.GYR_CROSS_YZ, cross[1]);
features.put(Feature.GYR_CROSS_ZX, cross[2]);
features.put(Feature.GYR_CROSS_XY_ABS, Math.abs(cross[0]));
features.put(Feature.GYR_CROSS_YZ_ABS, Math.abs(cross[1]));
features.put(Feature.GYR_CROSS_ZX_ABS, Math.abs(cross[2]));
break;
}
}
else
Log.e("PR",
"FeatureExtractor: Calculating cross-dimensional inner-products for a non-3D signal - values set to zero!");
}
if (this._hasNormCross)
{
double[] crossNorm = new double[this._dimensions];
for (int i = 0; i < this._dimensions; i++)
crossNorm[i] = 0;
if (this._dimensions == 3)
{
crossNorm = this.get3DNormInnerProds(signal);
switch (clip.getType())
{
case Clip.ACCELEROMETER:
features.put(Feature.ACC_CROSS_XY_NORM, crossNorm[0]);
features.put(Feature.ACC_CROSS_YZ_NORM, crossNorm[1]);
features.put(Feature.ACC_CROSS_ZX_NORM, crossNorm[2]);
features.put(Feature.ACC_CROSS_XY_NORM_ABS, Math.abs(crossNorm[0]));
features.put(Feature.ACC_CROSS_YZ_NORM_ABS, Math.abs(crossNorm[1]));
features.put(Feature.ACC_CROSS_ZX_NORM_ABS, Math.abs(crossNorm[2]));
break;
case Clip.GYROSCOPE:
features.put(Feature.GYR_CROSS_XY_NORM, crossNorm[0]);
features.put(Feature.GYR_CROSS_YZ_NORM, crossNorm[1]);
features.put(Feature.GYR_CROSS_ZX_NORM, crossNorm[2]);
features.put(Feature.GYR_CROSS_XY_NORM_ABS, Math.abs(crossNorm[0]));
features.put(Feature.GYR_CROSS_YZ_NORM_ABS, Math.abs(crossNorm[1]));
features.put(Feature.GYR_CROSS_ZX_NORM_ABS, Math.abs(crossNorm[2]));
break;
}
}
else
Log.e("Warning",
"Calculating cross-dimensional inner-products for a non-3D signal - values set to zero!");
}
// TODO: MAX, MIN, ABS_MAX, ABS_MIN, RMS?
/*
* if (hasFFT) Complex[] FFTvalues = fft.transform(signal,
* TransformType.FORWARD);
*
* // int i=0;
*/
if (this._hasFFT)
{
// TODO: Update & uncomment FFT code below...
switch (clip.getType())
{
case Clip.ACCELEROMETER:
features.put(Feature.ACCX_FFT1, 0.0);
features.put(Feature.ACCX_FFT2, 0.0);
features.put(Feature.ACCX_FFT3, 0.0);
features.put(Feature.ACCX_FFT4, 0.0);
features.put(Feature.ACCX_FFT5, 0.0);
features.put(Feature.ACCX_FFT6, 0.0);
features.put(Feature.ACCX_FFT7, 0.0);
features.put(Feature.ACCX_FFT8, 0.0);
features.put(Feature.ACCX_FFT9, 0.0);
features.put(Feature.ACCX_FFT10, 0.0);
features.put(Feature.ACCY_FFT1, 0.0);
features.put(Feature.ACCY_FFT2, 0.0);
features.put(Feature.ACCY_FFT3, 0.0);
features.put(Feature.ACCY_FFT4, 0.0);
features.put(Feature.ACCY_FFT5, 0.0);
features.put(Feature.ACCY_FFT6, 0.0);
features.put(Feature.ACCY_FFT7, 0.0);
features.put(Feature.ACCY_FFT8, 0.0);
features.put(Feature.ACCY_FFT9, 0.0);
features.put(Feature.ACCY_FFT10, 0.0);
features.put(Feature.ACCZ_FFT1, 0.0);
features.put(Feature.ACCZ_FFT2, 0.0);
features.put(Feature.ACCZ_FFT3, 0.0);
features.put(Feature.ACCZ_FFT4, 0.0);
features.put(Feature.ACCZ_FFT5, 0.0);
features.put(Feature.ACCZ_FFT6, 0.0);
features.put(Feature.ACCZ_FFT7, 0.0);
features.put(Feature.ACCZ_FFT8, 0.0);
features.put(Feature.ACCZ_FFT9, 0.0);
features.put(Feature.ACCZ_FFT10, 0.0);
break;
case Clip.GYROSCOPE:
features.put(Feature.GYRX_FFT1, 0.0);
features.put(Feature.GYRX_FFT2, 0.0);
features.put(Feature.GYRX_FFT3, 0.0);
features.put(Feature.GYRX_FFT4, 0.0);
features.put(Feature.GYRX_FFT5, 0.0);
features.put(Feature.GYRX_FFT6, 0.0);
features.put(Feature.GYRX_FFT7, 0.0);
features.put(Feature.GYRX_FFT8, 0.0);
features.put(Feature.GYRX_FFT9, 0.0);
features.put(Feature.GYRX_FFT10, 0.0);
features.put(Feature.GYRY_FFT1, 0.0);
features.put(Feature.GYRY_FFT2, 0.0);
features.put(Feature.GYRY_FFT3, 0.0);
features.put(Feature.GYRY_FFT4, 0.0);
features.put(Feature.GYRY_FFT5, 0.0);
features.put(Feature.GYRY_FFT6, 0.0);
features.put(Feature.GYRY_FFT7, 0.0);
features.put(Feature.GYRY_FFT8, 0.0);
features.put(Feature.GYRY_FFT9, 0.0);
features.put(Feature.GYRY_FFT10, 0.0);
features.put(Feature.GYRZ_FFT1, 0.0);
features.put(Feature.GYRZ_FFT2, 0.0);
features.put(Feature.GYRZ_FFT3, 0.0);
features.put(Feature.GYRZ_FFT4, 0.0);
features.put(Feature.GYRZ_FFT5, 0.0);
features.put(Feature.GYRZ_FFT6, 0.0);
features.put(Feature.GYRZ_FFT7, 0.0);
features.put(Feature.GYRZ_FFT8, 0.0);
features.put(Feature.GYRZ_FFT9, 0.0);
features.put(Feature.GYRZ_FFT10, 0.0);
break;
}
}
return features;
}
private List<double[]> interpolate(List<double[]> signal, List<Long> ts, int freq)
{
List<double[]> signalOut = new ArrayList<>();
if (ts.size() < 2) // CJK TODO: Ok returning uninitialized signalOut?
return signalOut;
double stepSize = 1e9 / (double) freq; // step size in nanosec
// checking if the timestamps are incremental - if not, the datapoint is
// removed
List<Long> t2 = new ArrayList<>();
List<double[]> signal2 = new ArrayList<>();
t2.add(ts.get(0));
signal2.add(Arrays.copyOf(signal.get(0), signal.get(0).length));
// TODO: Pull out into own variable: ts.size()
for (int j = 1; j < ts.size(); j++)
{
// TODO: Pull out into own variable: ts.get(j)
// TODO: Pull out into own variable: t2.size()
if (ts.get(j) > t2.get(t2.size() - 1))
{
t2.add(ts.get(j));
// TODO: Pull out into own variable: signal.get(j)
signal2.add(Arrays.copyOf(signal.get(j), signal.get(j).length));
}
else
{
Log.e("PR", "FeatureExtractor: Non-increasing timestamp found!");
}
}
// converting time instances to double and getting rid of big numbers
long tStart = t2.get(0);
double[] tDouble = new double[signal2.size()];
// TODO: Pull out into own variable: signal2.size()
for (int j = 0; j < signal2.size(); j++)
tDouble[j] = t2.get(j) - tStart;
// calculating the number of interpolated samples
int nSamp = (int) Math.floor(tDouble[signal2.size() - 1] / stepSize);
// creating new, regular time instances
// TODO: Reminder - pull out into own variable: signal2.size()
double[] tNew = new double[nSamp];
for (int j = 0; j < nSamp; j++)
tNew[j] = tDouble[signal2.size() - 1] - (double) j * stepSize;
double[][] signalOutTemp = new double[nSamp][this._dimensions];
for (int i = 0; i < this._dimensions; i++)
{
// building a separate array for the current axis
double[] signal1D = new double[signal2.size()];
for (int j = 0; j < signal2.size(); j++)
signal1D[j] = signal2.get(j)[i];
// spline interpolation
SplineInterpolator interp = new SplineInterpolator();
PolynomialSplineFunction func = interp.interpolate(tDouble, signal1D);
// interpolating onto new instances
for (int j = 0; j < nSamp; j++)
signalOutTemp[j][i] = func.value(tNew[j]);
}
// TODO: Pull out into own variable: signalOut.size()
for (int i = 0; i < nSamp; i++)
{
signalOut.add(new double[this._dimensions]);
signalOut.set(signalOut.size() - 1, signalOutTemp[i]);
}
return signalOut;
}
private List<double[]> getDiff(List<double[]> signal)
{
List<double[]> signalDiff = new ArrayList<>();
// TODO: Pull out into own variable: signal.size()
for (int i = 0; i < signal.size() - 1; i++)
{
double[] sig = signal.get(i);
double[] sigNext = signal.get(i + 1);
double[] sigDiff = new double[sig.length];
for (int j = 0; j < sig.length; j++)
sigDiff[j] = sigNext[j] - sig[j];
signalDiff.add(sigDiff);
}
return signalDiff;
}
private List<double[]> getZScore(List<double[]> signal, double[] mean, double[] std)
{
List<double[]> signalZScore = new ArrayList<>();
// TODO: Pull out into own variable: signal.size()
for (int i = 0; i < signal.size(); i++)
{
double[] sig = signal.get(i);
double[] sigZScore = new double[this._dimensions];
for (int j = 0; j < this._dimensions; j++)
sigZScore[j] = (sig[j] - mean[j]) / std[j];
signalZScore.add(sigZScore);
}
return signalZScore;
}
// This method will calculate mean, standard deviation, skewness, and
// kurtosis.
// each member of the list is one statistical moment, which consists of an
// array, with
// each element accounting for one dimension
private double[] getMoments(List<double[]> signal, int axis)
{
double[][] signalArray = new double[signal.size()][this._dimensions];
for (int i = 0; i < signalArray.length; i++)
{
signalArray[i] = signal.get(i);
}
if (signalArray.length < 2)
{
double[] out =
{ 0.0, 0.0, 0.0, 0.0 };
return out;
}
double sum = 0.0;
// For some reason, the following commented-out code generates
// "concurrent modification exception"!
// for (double[] value : signal)
// sum += value[axis];
// CJK TODO: Solve mystery ^
for (double[] aSignalArray : signalArray) sum += aSignalArray[axis];
double mean = sum / signalArray.length;
double m2 = 0.0;
double m3 = 0.0;
double m4 = 0.0;
double t2 = 0.0;
double t3 = 0.0;
double t4 = 0.0;
for (double[] aSignalArray : signalArray) {
t2 = (aSignalArray[axis] - mean) * (aSignalArray[axis] - mean);
m2 += t2;
t3 = t2 * (aSignalArray[axis] - mean);
m3 += t3;
t4 = t3 * (aSignalArray[axis] - mean);
m4 += t4;
}
double std = Math.sqrt(m2 / (signalArray.length - 1)); // unbiased
m2 /= signalArray.length;
m3 /= signalArray.length;
m4 /= signalArray.length;
double skewness = m3 / (std * std * std); // unbiased
double kurtosis = m4 / (m2 * m2) - 3; // unbiased
double out[] =
{ mean, std, skewness, kurtosis };
return out;
}
// overall mean of squares
private double getOverallMean(List<double[]> signal)
{
double[][] signalArray = new double[signal.size()][this._dimensions];
for (int i = 0; i < signalArray.length; i++)
{
signalArray[i] = signal.get(i);
}
double ms = 0;
// TODO: Pull out into own variable: signal.size()
/*
* for (int i = 0; i < signal.size(); i++) { for (int j = 0; j <
* this._dimensions; j++) ms += signal.get(i)[j] * signal.get(i)[j] /
* this._dimensions; }
*/
for (double[] aSignalArray : signalArray) {
for (int j = 0; j < this._dimensions; j++)
ms += aSignalArray[j] * aSignalArray[j] / this._dimensions;
}
ms /= signalArray.length;
return ms;
}
private double getRMS(List<double[]> signal, int axis)
{
double rms = 0;
// TODO: Pull out into own variable: signal.size()
for (int i = 0; i < signal.size(); i++)
rms += signal.get(i)[axis] * signal.get(i)[axis];
rms /= (double) signal.size();
return rms;
}
private double getMax(List<double[]> signal, int axis)
{
if (signal.size() == 0) // CJK Question / TODO: Is this always the right
// answer?
return 0;
double max = signal.get(0)[axis];
// TODO: Pull out into own variable: signal.size()
for (int i = 1; i < signal.size(); i++)
{
if (max < signal.get(i)[axis])
max = signal.get(i)[axis];
}
return max;
}
private double getMin(List<double[]> signal, int axis)
{
if (signal.size() == 0) // CJK Question / TODO: Is this always the right
// answer?
return 0;
double min = signal.get(0)[axis];
// TODO: Pull out into own variable: signal.size()
for (int i = 1; i < signal.size(); i++)
{
if (min > signal.get(i)[axis])
min = signal.get(i)[axis];
}
return min;
}
private double[] get3DInnerProds(List<double[]> signal)
{
// This feature only works for 3D signals (acceleration, magnetic field,
// etc).
double[] innerProds = new double[3];
if (this._dimensions != 3)
return innerProds; // double-check for dimension
// CJK Question / TODO: what's in the uninitialized double above^?
innerProds[0] = 0;
// TODO: Pull out into own variable: signal.size()
for (int j = 0; j < signal.size(); j++)
innerProds[0] += signal.get(j)[0] * signal.get(j)[1];
innerProds[0] /= (double) signal.size(); // mean
innerProds[1] = 0;
for (int j = 0; j < signal.size(); j++)
innerProds[1] += signal.get(j)[1] * signal.get(j)[2];
innerProds[1] /= (double) signal.size(); // mean
innerProds[2] = 0;
for (int j = 0; j < signal.size(); j++)
innerProds[2] += signal.get(j)[2] * signal.get(j)[0];
innerProds[2] /= (double) signal.size(); // mean
return innerProds;
}
private double[] get3DNormInnerProds(List<double[]> signal)
{
double[] innerProds = new double[3];
if (this._dimensions != 3)
return innerProds; // double-check for dimension
double[] magnitude = new double[signal.size()];
for (int j = 0; j < signal.size(); j++)
{
magnitude[j] = (signal.get(j)[0] * signal.get(j)[0]) + (signal.get(j)[1] * signal.get(j)[1])
+ (signal.get(j)[2] * signal.get(j)[2]);
}
innerProds[0] = 0;
for (int j = 0; j < signal.size(); j++)
innerProds[0] += signal.get(j)[0] * signal.get(j)[1] / magnitude[j];
innerProds[0] /= (double) signal.size(); // mean
innerProds[1] = 0;
for (int j = 0; j < signal.size(); j++)
innerProds[1] += signal.get(j)[1] * signal.get(j)[2] / magnitude[j];
innerProds[1] /= (double) signal.size(); // mean
innerProds[2] = 0;
for (int j = 0; j < signal.size(); j++)
innerProds[2] += signal.get(j)[2] * signal.get(j)[0] / magnitude[j];
innerProds[2] /= (double) signal.size(); // mean
return innerProds;
}
}